<?php
	// ############################
	// # Classe com funções MySQL #
	// ############################
	require_once(dirname(__FILE__).'/incluir.php');
	class MySQLController
	{
		protected $DB_host;
		protected $DB_user;
		protected $DB_password;
		protected $DB_schema;
		protected $charset;
		protected $tabela;
		protected $persistent;
		
		public $link;
		public $num_rows; // Se o comando for SELECT, vale o número de linhas retornadas, Se for SELECT, UPDATE ou DELETE, vale o número de linhas modificadas
		public $result; // Vale o retorno de mysql_query
		
		public function __construct($tabela_val = null, $persistent = true, $DB_host_val = null, $DB_user_val = null, $DB_password_val = null, $DB_schema_val = null, $charset = 'utf8')
		{
			// valores que tem que ficar para as ações de conexão
			$this->tabela = $tabela_val;
			$this->persistent = $persistent;
			
			if($DB_host_val != null && $DB_user_val != null && $DB_password_val != null && $DB_schema_val != null)
			{
				$this->DB_connect($DB_host_val, $DB_user_val, $DB_password_val, $DB_schema_val, $charset);
			}
			else
			{
				require(dirname(__FILE__).'/conexao.php');
				$this->DB_connect($DB_host, $DB_user, $DB_password, $DB_schema, $charset);
			}
		}
		
		// Conecta à base de dados passada por parametro com a codificação padrão utf-8
		public function DB_connect($DB_host, $DB_user, $DB_password, $DB_schema, $charset = 'utf8') {
			//Copia os parametros para as variaveis internas
			$this->DB_host = $DB_host;
			$this->DB_user = $DB_user;
			$this->DB_password = $DB_password;
			$this->DB_schema = $DB_schema;
			$this->charset = $charset;
			
			// É verificado o tipo de conexão que deve ser feita
			if($this->persistent == true)
				$this->link = mysql_pconnect($DB_host, $DB_user, $DB_password) or die('Não foi possível conectar ao banco de dados (DB_connect) : ' . mysql_error());
			else
				$this->link = mysql_connect($DB_host, $DB_user, $DB_password) or die('Não foi possível conectar ao banco de dados (DB_connect) : ' . mysql_error());
			
			mysql_select_db($DB_schema) or die('Não foi possível acessar a base de dados (DB_connect): ' . mysql_error());
			mysql_set_charset($charset, $this->link);
		}
		
		// Para retornar o resultado da query de INSERT, UPDATE, DELETE
		private function modify($sql) {
	      $this->result = mysql_query($sql, $this->link) or die("Query error<br/>".mysql_error($this->link));
	      $this->num_rows = mysql_affected_rows($this->link);
	      return $this->result;
	   }
	   
		// Para retornar o resultado da query de SELECT e SHOW
		private function executa_query($sql, $unique = false, $chaves_numericas = false)
		{
			$this->result = mysql_query($sql, $this->link) or die("Query error<br/>".mysql_error($this->link));
			$this->num_rows = mysql_num_rows($this->result);
			if ($unique == null || $unique == false) {
				if ($this->num_rows > 0) {
					// Para cada linha retornada em $this->result (o resultSet)
					for($i = 0; $i < $this->num_rows; $i++)
						if($chaves_numericas)
							$result[] = mysql_fetch_row($this->result);
						else
							$result[] = mysql_fetch_assoc($this->result); // Insere cada linha no final desse array $result
					return $result;
				}
			}
			// Se for um resultado só
			else if ($this->num_rows == 1) {
				if($chaves_numericas)
					return  mysql_fetch_row($this->result);
				else
					return mysql_fetch_assoc($this->result);
			}
			return array(); // Se não executou nada retorna um array vazio
		}
		
	   	/**
	   	 * Executa uma query no banco
	   	 * @param sql a string com o código da query em si
	   	 * @param unique se é um único resultado, vale true, se for false é porque é para retornar vários resultados
	   	 * @param modify vale true se é uma query de INSERT, UPDATE, DELETE e false se é uma query de SELECT ou SHOW
	   	 * @param chaves_numericas significa que usará mysql_fetch_row em vez de mysql_fetch_assoc (para os casos que modify é false)
	   	 */
		public function query($sql, $unique = false, $modify = false, $chaves_numericas = false)
		{
			// Se não está conectado
			if(!isset($this->link) || gettype($this->link) != "mysql link")
			{
				$this->DB_connect($this->DB_host, $this->DB_user, $this->DB_password, $this->DB_schema, $this->charset); // conecta
			}
			if($modify){ // Se for para modificar alguma tabela
				return $this->modify($sql); // Chama a função apropriada para modificar
			}
			// Se não executa a função, que retornará o resultado de uma chamada a uma query de SELECT ou SHOW
			return $this->executa_query($sql, $unique, $chaves_numericas);
		}
		
		// Retorna o numero de registros da tabela
		public function num_linhas($tabela = null)
		{
			$tabela = ($tabela == null) ? $this->tabela : $tabela;
			if($tabela == null) return false;
			$rows = $this->query('SELECT * FROM '. $tabela . ';');
			if(isset($rows)) // Se houve um retorno, então existe um número de linhas que estará em $this->num_rows
				return $this->num_rows; // Retorna esse número de linhas
			return false;
		}
		
		//Fecha a conexão MySQL
		public function close()
		{
			if(isset($this->link) && gettype($this->link) == "mysql link")
			{
				mysql_close($this->link);
				unset($this->link);
			}
		}
		
		// recebe uma string e ajeita o seu valor para inserir no BD
		public function ajeita_valor_string($str)
		{
			$str = mysql_real_escape_string($str, $this->link);
			return "'".$str."'";
		}
		
		// Pega o ultimo id da tabela, normalemente inserido por um comando INSERT
		public function last_id_inserted()
		{
			return mysql_insert_id($this->link);
		}
		
		// Carrega os campos da tabela e os seus tipos
		public function load_fields(&$fields, &$types)
		{
			$tabela = ($tabela == null) ? $this->tabela : $tabela;
			$rows = $this->query('SHOW COLUMNS FROM '. $tabela) or die('Não foi possível conectar ao banco de dados : ' . mysql_error());
			if($this->num_rows > 0) { // Se tiveram pelo menos uma linha de resultado
				// Coloca o valor de $types e de $fields baseado nos valores retornados para $rows
				foreach($rows as $row){
					array_push($fields, $row['Field']);
					$types[ $row['Field'] ] = $row['Type'];
				}
			}
		}
		
		// Retorna verdadeiro se o login e senha forem validos e falso no caso contrário
		public function login($login, $senha, $tabela = null, $nome_campo_login= null) {
			$tabela = ($tabela == null) ? $this->tabela : $tabela;
			if($tabela == null) return false;
			$query = "SELECT * FROM $tabela WHERE " . $nome_campo_login. ' = "'. $login .'" AND senha = "'. md5($senha) .'";';
			$result = $this->query($query, true); // Pega o registro da pessoa logada 
			if($this->num_rows == 1) // Se apenas um valor foi retornado, retorna esse valor
				return $result;
			else // Se não quer dizer que falhou
				return false;
		}
		
		// Retorna um array contendo uma lista, com os nomes das tabelas que estão no banco de dados atual
		public function get_tables_from_database()
		{
			$query = "SHOW TABLES FROM $this->DB_schema;"; // SQL para retornar a listas de tabelas do banco de dados atual
			$tables_result = $this->query($query, false, false, true); // valerá um array sendo que cada valor é um array da forma: [0] => nome_tabela
			$tables = array(); // vai ser um array só com a lista de tabelas
			foreach($tables_result as $table)
			{
				array_push($tables, $table[0]); // o primeiro elemento já é o nome da tabela
			}
			return $tables;
		}
		
		// Ve se ele já especificou um tipo de comparador
		private function get_comparator(&$chave)
		{
			$comparators = array('>=', '<=', '<>', '>', '<', 'LIKE');
			$cmp = '='; // o comparador a ser retornado
			
			foreach($comparators as $comparator)
			{
				if(strstr($chave, $comparator) != false) // se ele especificou algum comparador
				{
					$cmp = $comparator;
					$chave = str_replace($cmp, '', $chave); // tiro o valor do comparador da chave
					break;
				}
			}
			
			return $cmp;
		}
		
		// Monta a parte que escolhe quais os campos que irão retornar
		private function escolhe_campos(&$query, $fields)
		{
			if(empty($fields)) {
				$query .= '*';
			} else {
				$first = true;
				foreach($fields as $chave => $valor) {
					if($first){
						// Não sendo um numero: $chave é o nome do campo e o $valor é o nome que será associado a esse campo
						if(!is_numeric($chave))	{ 
							$query .= "$chave AS $valor";
						} else {
							$query .= $valor;
						}
						$first = false;
					}
					else {
						$query .= ', '; // Não sendo o primeiro item, é necessário a vírgula
						// Não sendo um numero: $chave é o nome do campo e o $valor é o nome que será associado a esse campo
						if(!is_numeric($chave))	{ 
							$query .= "$chave AS $valor";
						} else {
							$query .= $valor;
						}
					}
				}
			}
		}
		
		// Monta a parte das condições do WHERE
		private function add_where(&$query, $where)
		{
			$query .= 'WHERE ';
			$first = true;
			foreach($where as $chave => $valor)
			{
				$cmp = $this->get_comparator($chave);
				$valor = $this->ajeita_valor_string($valor);
				if($first) {
					$query .= "$chave $cmp $valor";
					$first = false;
				}
				else {
					$query .= " AND $chave $cmp $valor";
				}
			}
		}
		
		// Pega uma row da tabela dada por parametro com o id também passado por parametro
		public function get_row($id = null, $fields = null, $tabela = null, $where = null) {
			$id = isset($id) ? $id : 1; // Caso não seja passado o id pegará o primeiro registro			
			$tabela = ($tabela == null) ? $this->tabela : $tabela;
			if($tabela == null) return false;
			
			$query = 'SELECT ';
			
			$this->escolhe_campos($query, $fields);
			
			$query .= ' FROM '. $tabela. ' ';
			
			if(isset($where)) // Se o usuário definiu as opções de Where
			{
				$this->add_where($query, $where);
			}
			else // Se não pega o valor de $id
			{
				if(!is_array($id)) { // Se for somente um valor, assume que é o id			
					$query .= ' WHERE id = '. $id;
				}
				else { // Se não será um campo e o seu valor
					$campo = key($id); // Pego a chave
					$valor = $id[$campo]; // Pego o valor
					$valor = $this->ajeita_valor_string($valor); // Coloco aspas simples nele
					$query .= " WHERE $campo = $valor";
				}
			}
			
			$query .= ';';
			return $this->query($query, true); // Executa e retorna o valor da query feita
		}
		
		// Pega apenas o valor de um campo de apenas uma linha
		public function get_element($element = null, $id = null, $tabela = null, $where = null)
		{
			$element = isset($element) ? $element : 'id'; // O valor default é retornar o id
			$row = $this->get_row($id, array($element), $tabela, $where);
			return $row[$element];
		}
		
		// Pega rows seguidas da tabela passada por parametro com o ORDER BY, ORDER, LIMIT e OFFSET passados também por parametro
		public function get_rows($fields = null, $tabela = null, $where = null, $orderBy = null, $order = null, $limit = null, $offset = null, $campo_indice_array = null) {
			$rows = array();
			
			$orderBy = isset($orderBy) ? $orderBy : 'id';
			$order = isset($order) ? $order : 'ASC';
			
			$tabela = ($tabela == null) ? $this->tabela : $tabela;
			if($tabela == null) return false;
			
			$campo_indice_array = isset($campo_indice_array) ? $campo_indice_array : 'id'; // o valor default para o indice é o id da tabela
			
			$query = 'SELECT ';
			
			$this->escolhe_campos($query, $fields);

			$query .= ' FROM '. $tabela . ' ';

			if(isset($where)) // Se o usuário definiu as opções de Where
			{
				$this->add_where($query, $where);
			}
			
			if(!empty($orderBy))
				$query .= ' ORDER BY '. $orderBy;
					
			if(!empty($order))
				$query .= ' '. $order;
				
			if(!empty($limit))
				$query .= ' LIMIT '. $limit;

			if(!empty($offset))
				$query .= ' OFFSET '. $offset;
			
			$query .= ';';
			return $this->query($query); // Executa e retorna o valor da query feita
		}
		
		// Pega apenas o valor de um campo das linhas retornadas, tudo em um array só
		public function get_elements($element = null, $tabela = null, $where = null, $orderBy = null, $order = null, $limit = null, $offset = null, $campo_indice_array = null)
		{
			$element = isset($element) ? $element : 'id'; // O valor default é retornar o id
			$rows = $this->get_rows(array($element), $tabela, $where, $orderBy, $order, $limit, $offset);
			$rows_element = array(); // Guardará o array final, que será uma lista dos valores do campo $element do BD
			foreach($rows as $index => $row)
			{
				$rows_element[$index] = $row[$element];
			}
			return $rows_element;
		}
		
		// Dependendo do tipo do valor do campo $field, irá atribuir o valor passado:
		// Ou entre aspas simples ou sem aspas simples, para o chave $field do array $dados. 
		// Ocorre também o caso especial de criptografar a senha
		private function atribui_valor_correto(&$dados, $type, $field, $valor)
		{
			// Contém um caso especial para o campo senha

			if($valor == 'DEFAULT') // se for para ser o valor DEFAULT do banco não modifica esse valor
			{
				$dados[$field] = $valor;
			}
			else if($valor == 'now()') // caso especial de ser um valor para a data, que é para criar agora
			{
				$dados[$field] = $valor;
			}
			else // Se não for um campo data atual e o valor não for o valor DEFAULT do banco de dados
			{
				// Valores que type pode valer: data, password, telefone, text, textarea
				if(strstr($type, 'password') != false)
				{
					$dados[$field] = "md5('".$valor."')";
				}
				else if(strstr($type, 'data') != false || strstr($type, 'date') != false )
				{
					$site = new Site();
					$dados[$field] = $this->ajeita_valor_string($site->data_BD($valor));
				}
				// Se esse if acontecer significa que o valor da variável $valor,
				// tem que estar entre aspas simples ('') e sem caracteres especiais
				else if( strstr($type, 'text') != false || strstr($type, 'textarea') != false ||
					 stristr($type, 'telefone') != false || strstr($type, 'radio') != false ||
					 strstr($type, 'checkbox') != false || strstr($type, 'select') != false)
				{
					$dados[$field] = $this->ajeita_valor_string($valor);
				} 
				else // Se não simplesmente copia o valor de $valor 
				{
					$dados[$field] = $valor;
				}
			}		
		}
		
		// Cada campo será passado para atribui_valor_correto.
		// Porém decidindo se deve passar o valor default ou o valor do array $dados
		private function atribui_valores_corretos(&$dados, $fields,  $campos)
		{
			// Eu percorro o array $fields para saber os campos que tem na tabela para inserir
			// e uso o array $campos para saber o tipo do campo e o seu valor default
			foreach($fields as $field)
			{
				$campo = $campos[$field];
				$type = $campo->tipo;
				$valor_default = $campo->valor_default;
				// Verifica se dados contém o valor do campo
				if( isset($dados[$field]) )
				{
					$this->atribui_valor_correto($dados, $type, $field, $dados[$field]); // Manda ajeitar o valor do campo para inserir no BD
				}
				else // Se o post não contém o valor do campo, atribuirá um valor default ao campo
				{
					// Verifica se existe um valor default para esse campo
					if( isset($valor_default) )
					{
						// Se contiver o valor default, manda copiar esse valor para o array $this->dados
						$this->atribui_valor_correto($dados, $type, $field, $valor_default);
					}
					else // Se não contiver o valor default
					{
						$this->atribui_valor_correto($dados, $type, $field, 'DEFAULT'); // Manda atribuir o valor DEFAULT do BD
					}
				}
			}
		}
		
		// A versão do atribui_valores_corretos para alterar os dados.
		// Em vez de usar os valores default dos campos, se o campo não tiver um valor definido, deixa o que estava, ou seja, não inclui na query
		private function atribui_valores_corretos_alterar(&$dados, $fields,  $campos)
		{
			// Eu percorro o array $fields para saber os campos que tem na tabela para inserir
			// e uso o array $campos para saber o tipo do campo e o seu valor default
			foreach($fields as $field)
			{
				$campo = $campos[$field];
				$type = $campo->tipo;
				$valor_default = $campo->valor_default;
				// Verifica se dados contém o valor do campo
				if( isset($dados[$field]) )
				{
					$this->atribui_valor_correto($dados, $type, $field, $dados[$field]); // Manda ajeitar o valor do campo para inserir no BD
				}
				else // Se o post não contém o valor do campo, irá tira-lo do array de dados
				{
					unset($dados[$field]);
				}
			}
		}
		
		// Insere uma linha numa tabela, com os dados passados por parametro e a tabela também
		public function insert_row($dados, $fields = null, $campos = null, $tabela = null) {
			$cols = '';
			$rows = '';
			$tabela = ($tabela == null) ? $this->tabela : $tabela;
			if($tabela == null) return false;
			
			if(!empty($fields) && !empty($campos) != null){
				$this->atribui_valores_corretos($dados, $fields, $campos); // ajeita os valores contidos no array $dados
			}
			
			$first = true;
			foreach($dados as $index => $value) {
				if(!$first) {
					$cols .= ',';
					$rows .= ',';
				}
				else{
					$first = false;
				}
				$cols .= $index;
				$rows .= $value;
			}
			
			$query = 'INSERT INTO '. $tabela .' ('. $cols .') VALUES ('. $rows .');';
			return $this->query($query, true, true);
		}
		
		// Atualiza uma row com os dados passados por parametro e com o id e a tabela passados por parametro
		public function update_row($valor, $dados, $fields = null, $campos = null, $tabela = null, $campo = 'id') {
			$expressions = '';
			$tabela = ($tabela == null) ? $this->tabela : $tabela;
			if($tabela == null) return false;
			
			if(!empty($fields) && !empty($campos) != null){
				$this->atribui_valores_corretos_alterar($dados, $fields, $campos); // ajeita os valores contidos no array $dados
			}
			
			$first = true;
			foreach($dados as $index => $value) {
				if(!$first){
					$expressions .= ', ';
				}
				else{
					$first = false;
				}
				$expressions .= $index .' = '. $value;
			}
			
			$query = 'UPDATE '. $tabela .' SET '. $expressions .' ';
			$query .= 'WHERE '.$campo.' = '. $valor .';';
			
			return $this->query($query, true, true);
		}
		
		// Atualiza uma row com os dados passados por parametro e com o id e a tabela passados por parametro
		// Se varios nomes de campos forem passados (um array) eu faço para excluir baseado em todos serem daquele valor
		public function delete_row($valor, $tabela = null, $campos = 'id') {
			$tabela = ($tabela == null) ? $this->tabela : $tabela;
			if($tabela == null) return false;
			
			$query = 'DELETE FROM '. $tabela;
			if(is_array($campos))
			{
				$where = ' WHERE ';
				$first = true;
				foreach($campos as $campo)
				{
					if($first)
					{
						$where .= "$campo = $valor";
						$first = false;
					}
					else
					{
						$where .= " OR $campo = $valor";
					}
				}
			}
			else
			{
				$query .= ' WHERE '.$campos.' = '. $valor;
			}
			$query .= ';';
			return $this->query($query, true, true);
		}
		
		// Essa função facilita as chamadas ao get_rows e get_row
		public function __call($name, $arguments) {
	        if(strstr($name, 'get_row_by_'))
	        {
	        	$name = str_replace('get_row_by_', '',$name); // Tira o inicio da string, sobrando somente o nome do campo
	        	$valor_campo = $arguments[0];
	        	$fields = isset($arguments[1]) ? $arguments[1] : null;
	        	$tabela = isset($arguments[2]) ? $arguments[2] : null;
	        	$where = isset($arguments[3]) ? $arguments[3] : null;
	        	return $this->get_row(array($name => $valor_campo), $fields, $tabela, $where);
	        }
	        else if(strstr($name, 'get_rows_by_'))
	        {
	        	$name = str_replace('get_rows_by_', '',$name); // Tira o inicio da string, sobrando somente o nome do campo
	        	$where = array($name => $arguments[0]);
	        	$fields = isset($arguments[1]) ? $arguments[1] : null;
	        	$tabela = isset($arguments[2]) ? $arguments[2] : null;
	        	$orderBy = isset($arguments[3]) ? $arguments[3] : null;
	        	$order = isset($arguments[4]) ? $arguments[4] : null;
	        	$limit = isset($arguments[5]) ? $arguments[5] : null;
	        	$offset = isset($arguments[6]) ? $arguments[6] : null;
	        	return $this->get_rows($fields, $tabela, $where, $orderBy, $order, $limit, $offset);
	        }
	        else
	        {
	        	die('Não existe essa função');
	        }
	    }
		
		// Quando o objeto é deletado, eu fecho a conexão que ele usava
		public function __destruct()
		{
			if($this->persistent != true)
			{
				$this->close();
			}
		}
	}
?>